/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.projects.csv.business.manager.impl;

import java.io.File;
import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.genia.toolbox.basics.bean.impl.FileDataContainer;
import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.basics.exception.technical.XmlException;
import com.genia.toolbox.basics.i18n.I18nMessageFactory;
import com.genia.toolbox.basics.manager.ExceptionManager;
import com.genia.toolbox.persistence.bean.Persistable;
import com.genia.toolbox.persistence.criteria.Criteria;
import com.genia.toolbox.persistence.dao.AbstractDAO;
import com.genia.toolbox.persistence.exception.ObjectIsNotPersistableException;
import com.genia.toolbox.persistence.exception.PersistenceException;
import com.genia.toolbox.persistence.jcr.manager.DataContainerManager;
import com.genia.toolbox.projects.csv.bean.Action;
import com.genia.toolbox.projects.csv.bean.Binder;
import com.genia.toolbox.projects.csv.bean.CsvDataRow;
import com.genia.toolbox.projects.csv.bean.EntityDescriptor;
import com.genia.toolbox.projects.csv.bean.FieldDescriptor;
import com.genia.toolbox.projects.csv.bean.FolderDescriptor;
import com.genia.toolbox.projects.csv.bean.MappingDescriptor;
import com.genia.toolbox.projects.csv.business.exception.CsvImportException;
import com.genia.toolbox.projects.csv.business.exception.MappingException;
import com.genia.toolbox.projects.csv.business.manager.CheckIfEntityIsNullBinderVisitor;
import com.genia.toolbox.projects.csv.business.manager.ComputeColumnNameVisitor;
import com.genia.toolbox.projects.csv.business.manager.CsvBackgroundProcess;
import com.genia.toolbox.projects.csv.business.manager.ExecutionActionVisitor;
import com.genia.toolbox.projects.csv.business.manager.FieldCommentsGenerator;
import com.genia.toolbox.projects.csv.business.manager.FillCriteriaBinderVisitor;
import com.genia.toolbox.projects.csv.business.manager.FillNewEntityBinderVisitor;
import com.genia.toolbox.projects.csv.business.util.EnumerationUtils;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.Language;
import com.genia.toolbox.projects.toolbox_basics_project.bean.model.impl.LanguageImpl;
import com.genia.toolbox.spring.provider.message.manager.CustomResourceBundleMessageSource;

/**
 * the implementation class of the {@link CsvBackgroundProcess} interface.
 */
@Transactional(readOnly = true)
public class CsvBackgroundProcessImpl
    extends AbstractDAO
    implements CsvBackgroundProcess, BeanFactoryAware
{
  /**
   * bad date format error key.
   */
  public static final String BAD_DATE_FORMAT_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() + ".dateformat.bad";

  /**
   * column comments message.
   */
  public static final String COLUMN_COMMENTS_KEY = CsvBackgroundProcessImpl.class.getName() + ".columnComments";

  /**
   * error when a file that must be imported does not exist.
   */
  public static final String NO_SUCH_FILE_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() + ".noSuchFile";

  /**
   * readme texts message.
   */
  public static final String README_TEXTS_KEY = CsvBackgroundProcessImpl.class.getName() + ".readmeTexts";

  /**
   * reference to nonexistent data error key.
   */
  public static final String REFERENCE_TO_NONEXISTENT_DATA_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() + ".reference.data.nonexistent";

  /**
   * unable to instanciate an entity.
   */
  public static final String UNABLE_TO_INSTANCIATE_ENTITY_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() + ".error.unableToInstanciateEntity";

  /**
   * directory in which to put the files to import.
   */
  private static final String FILES_SUBDIR = "files";

  /**
   * data for column not found error key. public static final String
   * DATACOLUMN_NOT_FOUND_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() +
   * ".dataColumn.notfound";
   */

  /**
   * field no getter error key. public static final String
   * FIELD_NOGETTER_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() +
   * ".field.nogetter";
   */

  /**
   * field no setter error key. public static final String
   * FIELD_NOSETTER_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() +
   * ".field.nosetter";
   */

  /**
   * reference to an entity with no fields error key. public static final String
   * REFERENCE_TO_ENTITY_WITH_NO_FIELDS_ERROR_KEY =
   * CsvBackgroundProcessImpl.class.getName() + ".reference.entity.nofields";
   */

  /**
   * many side relation attribute no add method error key. public static final
   * String RELATION_NOADDER_ERROR_KEY =
   * CsvBackgroundProcessImpl.class.getName() + ".relation.noadder";
   */

  /**
   * relation attribute no setter error key. public static final String
   * RELATION_NOSETTER_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() +
   * ".relation.nosetter";
   */

  /**
   * repeated column error message. public static final String
   * REPEATED_COLUMN_KEY = CsvBackgroundProcessImpl.class.getName() +
   * ".column.repeated";
   */

  /**
   * unique constraint violation error on a unique fied. public static final
   * String UNIQUE_CONSTRAINT_ERROR_KEY =
   * CsvBackgroundProcessImpl.class.getName() + ".field.unique.violation";
   */

  /**
   * unknown enum value error key. public static final String
   * UNKNOWN_CHOSEN_VALUE_ERROR_KEY = CsvBackgroundProcessImpl.class.getName() +
   * ".datacolumn.unknownvalue"; public static final String
   * UNKNOWN_ENTITY_INTERFACE_ERROR_KEY =
   * CsvBackgroundProcessImpl.class.getName() + ".entity.interface.unknown";
   */

  /**
   * unkown entity interface error key.
   */

  /**
   * the {@link DataContainerManager} to use.
   */
  private DataContainerManager dataContainerManager;

  /**
   * the {@link ExceptionManager} to use.
   */
  private ExceptionManager exceptionManager;

  /**
   * reference to the {@link CustomResourceBundleMessageSource}.
   */
  private CustomResourceBundleMessageSource messageSource;

  /**
   * the {@link FillCriteriaBinderVisitor} to use.
   */
  private FillCriteriaBinderVisitor fillCriteriaBinderVisitor;

  /**
   * the {@link FieldCommentsGenerator} to use.
   */
  private FieldCommentsGenerator fieldCommentsGenerator;

  /**
   * the {@link CheckIfEntityIsNullBinderVisitor} to use.
   */
  private CheckIfEntityIsNullBinderVisitor checkIfEntityIsNullBinderVisitor;

  /**
   * the {@link FillNewEntityBinderVisitor} to use.
   */
  private FillNewEntityBinderVisitor fillNewEntityBinderVisitor;

  /**
   * the {@link ExecutionActionVisitor} to use.
   */
  private ExecutionActionVisitor executionActionVisitor;

  /**
   * the {@link ComputeColumnNameVisitor} to use.
   */
  private ComputeColumnNameVisitor computeColumnNameVisitor;

  /**
   * the {@link EnumerationUtils} to use.
   */
  private EnumerationUtils enumerationUtils;

  /**
   * the {@link ConfigurableBeanFactory} to use to retrieve
   * {@link org.springframework.beans.TypeConverter}.
   */
  private transient ConfigurableBeanFactory beanFactory;



  /**
   * Callback that supplies the owning factory to a bean instance.
   * <p>
   * Invoked after the population of normal bean properties but before an
   * initialization callback such as
   * {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
   * or a custom init-method.
   * 
   * @param beanFactory
   *          owning BeanFactory (never <code>null</code>). The bean can
   *          immediately call methods on the factory.
   * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
   */
  public void setBeanFactory(BeanFactory beanFactory)
  {
    if (beanFactory != null && beanFactory instanceof ConfigurableBeanFactory) {
      this.beanFactory = (ConfigurableBeanFactory) beanFactory;
    }
  }



  /**
   * clean the database before importing new data version.
   * 
   * @param mappingDescriptor
   *          the {@link MappingDescriptor} describing the mapping
   * @throws PersistenceException
   *           if an error occur during the deletion process
   * @throws MappingException
   *           if a mapping error occur
   */
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = BundledException.class)
  @SuppressWarnings("unchecked")
  public void clean(MappingDescriptor mappingDescriptor)
      throws PersistenceException, MappingException
  {
    for (FolderDescriptor folderDescriptor : mappingDescriptor.getFolders()) {
      for (EntityDescriptor entityDescriptor : folderDescriptor.getEntities()) {
        for (Object entity : getAll(entityDescriptor.getEntityClass())) {
          delete((Persistable<? extends Serializable>) entity);
        }
      }
    }
  }



  /**
   * getter for the dataContainerManager property.
   * 
   * @return the dataContainerManager
   */
  public DataContainerManager getDataContainerManager()
  {
    return dataContainerManager;
  }



  /**
   * getter for the exceptionManager property.
   * 
   * @return the exceptionManager
   */
  public ExceptionManager getExceptionManager()
  {
    return exceptionManager;
  }



  /**
   * getter for the messageSource property.
   * 
   * @return the messageSource
   */
  public CustomResourceBundleMessageSource getMessageSource()
  {
    return messageSource;
  }



  /**
   * returns the name of the concrete column of the Cvs file associated to the
   * given folder.
   * 
   * @param folder
   *          the folder to consider
   * @return the name of the concrete column of the Cvs file associated to the
   *         given folder
   * @see CsvBackgroundProcess#getColumnNames(FolderDescriptor)
   */
  public List<String> getColumnNames(FolderDescriptor folder)
  {
    try {
      for (FieldDescriptor field : folder.getFields()) {
        field.visit(getComputeColumnNameVisitor());
      }
      return getComputeColumnNameVisitor().getColumnsNames();
    }
    finally {
      getComputeColumnNameVisitor().clear();
    }
  }



  /**
   * value to input for column that belongs to a field of type like enumeration
   * must be specified exactly like it's described in the corresponding
   * properties file for the main language; so, a comment will be writen for
   * each enumeration column to specify the possible values for the concerned
   * language. this method is used to get all needed comments for all column of
   * the given folder.
   * 
   * @param folder
   *          the {@link FolderDescriptor} to consider
   * @return the {@link List} of comments to write to the csv file after the
   *         column names headers.
   * @throws MappingException
   *           if a mapping error occur
   */
  public List<String> getNeededComments(FolderDescriptor folder)
      throws MappingException
  {

    List<String> comments = null;
    try {
      for (FieldDescriptor field : folder.getFields()) {
        field.visit(getFieldCommentsGenerator());
      }
      comments = fieldCommentsGenerator.getComments();
    }
    finally {
      getFieldCommentsGenerator().clear();
    }
    if (!comments.isEmpty()) {
      comments.add(0, " ");
      comments.add(1, getMessageSource().getMessage(I18nMessageFactory.getIl8nMessage(COLUMN_COMMENTS_KEY, new Object[0]), folder.getMappingDescriptor().getMainLocale()));
      comments.add(2, " ");
      comments.add(" ");
    }

    return comments;

  }



  /**
   * generate the readme texts recommandation for folder dependencies.
   * 
   * @param mappingDescriptor
   *          the {@link MappingDescriptor} describing the mapping
   * @return a {@link List} of string texts.
   * @throws XmlException
   *           if an xml error occur
   * @throws TechnicalIOException
   *           if an io error occur
   */
  public List<String> getReadmeDependencies(MappingDescriptor mappingDescriptor)
      throws XmlException, TechnicalIOException
  {

    Locale mainLocale = mappingDescriptor.getMainLocale();

    List<String> texts = new ArrayList<String>();

    texts.add(getMessageSource().getMessage(I18nMessageFactory.getIl8nMessage(README_TEXTS_KEY, new Object[0]), mainLocale));
    texts.add(" ");

    for (FolderDescriptor folderDescriptor : mappingDescriptor.getFolders()) {
      texts.add(folderDescriptor.getFolderName());
    }
    return texts;

  }



  /**
   * import the csv data rows for the given folder.
   * 
   * @param folderPath
   *          the file containing the data for the current folder
   * @param folder
   *          the concerned {@link FolderDescriptor}
   * @param acceptedDataRows
   *          a {@link List} of accepted {@link CsvDataRow}s of the folder,
   *          regarding to the considered file version
   * @throws BundledException
   *           if an error occured
   */
  @Transactional(rollbackFor = BundledException.class)
  public void importCsvDataRows(File folderPath, FolderDescriptor folder, List<CsvDataRow> acceptedDataRows)
      throws BundledException
  {
    CsvVariableResolver csvVariableResolver = new CsvVariableResolver();
    try {
      getExecutionActionVisitor().setCsvVariableResolver(csvVariableResolver);
      for (CsvDataRow dataRow : acceptedDataRows) {
        Map<String, Object> fieldsValues = getFieldsValues(folderPath, folder, dataRow);
        csvVariableResolver.setFieldsValues(fieldsValues);
        for (Map<String, Object> entitiesValues : getEntitiesValues(folder, fieldsValues, dataRow)) {
          csvVariableResolver.setEntitiesValues(entitiesValues);
          for (Action action : folder.getActions()) {
            action.visit(getExecutionActionVisitor());
          }
          clear();
        }
      }
    }
    finally {
      getExecutionActionVisitor().clearVisitor();
    }
  }



  /**
   * compute the entities values.
   * 
   * @param folder
   *          the concerned {@link FolderDescriptor}
   * @param fieldsValues
   *          the values of the fields
   * @param dataRow
   *          the row of data that was used to create the current entities
   * @return the computed entities values
   * @throws BundledException
   *           if an error occured
   */
  private List<? extends Map<String, Object>> getEntitiesValues(FolderDescriptor folder, Map<String, Object> fieldsValues, CsvDataRow dataRow)
      throws BundledException
  {
    @SuppressWarnings("unchecked")
    List<? extends Map<String, Object>> currentEntitiesValues = Arrays.asList(new HashMap<String, Object>());

    for (EntityDescriptor entity : folder.getEntities()) {
      currentEntitiesValues = getEntityValues(entity, fieldsValues, currentEntitiesValues, dataRow);
    }
    return currentEntitiesValues;
  }



  /**
   * compute the possible values for the entity given in parameter and returns
   * the differents possible values for all entities computed so far.
   * 
   * @param entity
   *          the {@link EntityDescriptor} describing the entity to compute
   * @param fieldsValues
   *          the values of the field of the csv
   * @param currentEntitiesValues
   *          the current values of the entities
   * @param dataRow
   *          the row of data that was used to create the current entities
   * @return the differents possible values for all entities computed so far
   * @throws BundledException
   *           if an error occured
   */
  private List<? extends Map<String, Object>> getEntityValues(EntityDescriptor entity, Map<String, Object> fieldsValues, List<? extends Map<String, Object>> currentEntitiesValues, CsvDataRow dataRow)
      throws BundledException
  {
    List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();

    // Define the variable used in the loop.

    try {
      getCheckIfEntityIsNullBinderVisitor().setFieldsValues(fieldsValues);

      Criteria<?> criteria = null;

      for (Map<String, Object> otherEntitiesValues : currentEntitiesValues) {
        getCheckIfEntityIsNullBinderVisitor().setEntitiesValues(otherEntitiesValues);

        criteria = getCriteria(entity.getEntityClass());

        try {
          getFillCriteriaBinderVisitor().setCriteria(criteria);
          getFillCriteriaBinderVisitor().setEntitiesValues(otherEntitiesValues);
          getFillCriteriaBinderVisitor().setFieldsValues(fieldsValues);
          for (Binder binder : entity.getBinders()) {
            binder.visit(getCheckIfEntityIsNullBinderVisitor());
            binder.visit(getFillCriteriaBinderVisitor());
          }
        }
        finally {
          getFillCriteriaBinderVisitor().clearVisitor();
        }

        if (getCheckIfEntityIsNullBinderVisitor().isEntityIsNull() || entity.getUnique()) {
          Object entityValue = null;
          if (!getCheckIfEntityIsNullBinderVisitor().isEntityIsNull()) {
            entityValue = simpleFindUnique(criteria);
          }
          if (entityValue == null) {
            if (entity.getErrorIfNull()) {
              throw new CsvImportException("reference to nonexistent data", REFERENCE_TO_NONEXISTENT_DATA_ERROR_KEY, dataRow.getLineNumber(), entity.getName());
            }
            if (entity.getCreateIfNull()) {
              entityValue = createNewEntity(entity, fieldsValues, otherEntitiesValues);
            }
          }

          otherEntitiesValues.put(entity.getName(), entityValue);
          result.add(otherEntitiesValues);
          break;
        }
        else {
          List<?> listEntities = simpleFind(criteria);
          if (listEntities.isEmpty()) {
            if (entity.getErrorIfNull()) {
              throw new CsvImportException("reference to nonexistent data", REFERENCE_TO_NONEXISTENT_DATA_ERROR_KEY, dataRow.getLineNumber(), entity.getName());
            }
            if (entity.getCreateIfNull()) {
              listEntities = Arrays.asList(createNewEntity(entity, fieldsValues, otherEntitiesValues));
            }
            else {
              listEntities = Arrays.asList((Object) null);
            }
          }
          for (Object currentEntityValue : listEntities) {
            Map<String, Object> newEntityValues = new HashMap<String, Object>(otherEntitiesValues);
            newEntityValues.put(entity.getName(), currentEntityValue);
            result.add(newEntityValues);
          }
        }

      }
    }
    finally {
      getCheckIfEntityIsNullBinderVisitor().clear();
    }
    return result;
  }



  /**
   * create a new entity using the {@link EntityDescriptor} and the needed
   * values of fields and dependant entities.
   * 
   * @param entity
   *          the {@link EntityDescriptor} describing the entity to compute
   * @param fieldsValues
   *          the values of the field of the csv
   * @param entitiesValues
   *          the current values of the entities
   * @return a new entity using the {@link EntityDescriptor} and the needed
   *         values of fields and dependant entities
   * @throws BundledException
   *           if an error occured
   */
  private Object createNewEntity(EntityDescriptor entity, Map<String, Object> fieldsValues, Map<String, Object> entitiesValues)
      throws BundledException
  {
    Persistable<?> o = null;
    try {
      o = (Persistable<?>) entity.getEntityClass().newInstance();
    }
    catch (ClassCastException e) {
      throw new ObjectIsNotPersistableException();
    }
    catch (Exception e) {
      throw new MappingException("Unable to instanciate class for entity " + entity.getName(), UNABLE_TO_INSTANCIATE_ENTITY_ERROR_KEY, entity.getName());
    }
    try {
      getFillNewEntityBinderVisitor().setEntitiesValues(entitiesValues);
      getFillNewEntityBinderVisitor().setEntity(o);
      getFillNewEntityBinderVisitor().setFieldsValues(fieldsValues);
      for (Binder binder : entity.getBinders()) {
        binder.visit(getFillNewEntityBinderVisitor());
      }
    }
    finally {
      getFillNewEntityBinderVisitor().clear();
    }

    saveOrUpdate(o);
    return o;
  }



  /**
   * import a {@link File} into jcr, and returns the Uuid to retrieve it.
   * 
   * @param folderPath
   *          the file containing the data for the current folder
   * @param fieldDescriptor
   *          the description of the field
   * @param fileName
   *          the content of the Csv file
   * @return the uuid of the imported file
   * @throws TechnicalException
   *           if an error occured
   * @throws CsvImportException
   *           if an error occured
   */
  private String importDirectory(final File folderPath, final FieldDescriptor fieldDescriptor, String fileName)
      throws TechnicalException, CsvImportException
  {

    File file = new File(new File(folderPath, FILES_SUBDIR), fileName);
    if (!file.exists()) {
      throw new CsvImportException("Unable to find file: " + file.getPath(), NO_SUCH_FILE_ERROR_KEY, file.getPath(), fieldDescriptor.getName());
    }

    FileDataContainer fileDataContainer = new FileDataContainer();
    fileDataContainer.setFile(file);

    String directoryUuid = getDataContainerManager().newDirectory();
    getDataContainerManager().uploadZipDataContainer(directoryUuid, fileDataContainer);
    return directoryUuid;
  }



  /**
   * import a {@link File} into jcr, and returns the Uuid to retrieve it.
   * 
   * @param folderPath
   *          the file containing the data for the current folder
   * @param fieldDescriptor
   *          the description of the field
   * @param fileDescription
   *          the content of the Csv file
   * @return the uuid of the imported file
   * @throws TechnicalException
   *           if an error occured
   * @throws CsvImportException
   *           if an error occured
   */
  private String importFile(final File folderPath, final FieldDescriptor fieldDescriptor, String fileDescription)
      throws TechnicalException, CsvImportException
  {
    StringTokenizer stringTokenizer = new StringTokenizer(fileDescription, "|");
    final String realFileName = stringTokenizer.nextToken();
    final String virtualFileName = stringTokenizer.hasMoreTokens() ? stringTokenizer.nextToken() : realFileName;
    final String contentType = stringTokenizer.hasMoreTokens() ? stringTokenizer.nextToken() : null;

    File file = new File(new File(folderPath, FILES_SUBDIR), realFileName);
    if (!file.exists()) {
      throw new CsvImportException("Unable to find file: " + file.getPath(), NO_SUCH_FILE_ERROR_KEY, file.getPath(), fieldDescriptor.getName());
    }
    FileDataContainer fileDataContainer = new FileDataContainer();
    fileDataContainer.setFile(file);
    fileDataContainer.setName(virtualFileName);
    fileDataContainer.setContentType(contentType);

    return getDataContainerManager().persistDataContainer(fileDataContainer);
  }



  /**
   * compute the fields values.
   * 
   * @param folderPath
   *          the file containing the data for the current folder
   * @param folder
   *          the concerned {@link FolderDescriptor}
   * @param dataRow
   *          the row to compute the values for
   * @return a {@link Map} associating each field to its value
   * @throws CsvImportException
   *           if there is an error in the CSV file
   * @throws TechnicalException
   *           if an error occured
   */
  @SuppressWarnings("unchecked")
  private Map<String, Object> getFieldsValues(File folderPath, FolderDescriptor folder, CsvDataRow dataRow)
      throws CsvImportException, TechnicalException
  {
    // Define the result.
    Map<String, Object> fieldsValues = new HashMap<String, Object>();

    // Define the variables that will be used in the loop.
    FieldDescriptor field = null;
    List<String> values = null;
    String singleValue = null;
    Object value = null;

    for (Entry<FieldDescriptor, List<String>> entry : dataRow.getDataRowMap().entrySet()) {
      field = entry.getKey();
      values = entry.getValue();
      value = null;
      singleValue = values.get(0);

      // Check that the field has not a null value.
      if (field.getNullRegularExpression() == null || !singleValue.matches(field.getNullRegularExpression())) {
        if (field.getFieldClass().isEnum()) {
          value = getEnumerationUtils().getEnumInstance(folder.getMappingDescriptor().getMainLocale(), (Class<Enum>) field.getFieldClass(), singleValue);
        }
        else {
          switch (field.getFieldType()) {
            case I18N: {
              Map<Locale, String> i18nValue = new HashMap<Locale, String>();
              int i = 0;
              for (Locale locale : folder.getMappingDescriptor().getSupportedLocales()) {
                i18nValue.put(locale, values.get(i));
                i++;
              }
              value = i18nValue;
              break;
            }
            case I18N_BIND: {
              Map<Locale, String> i18nValue = new HashMap<Locale, String>();
              i18nValue.put(folder.getMappingDescriptor().getMainLocale(), singleValue);
              value = i18nValue;
              break;
            }
            case DATE:
              try {
                value = folder.getMappingDescriptor().getDateFormat().parse(singleValue);
              }
              catch (ParseException e) {
                throw new CsvImportException("wrong date format", BAD_DATE_FORMAT_ERROR_KEY, singleValue, field.getName(), dataRow.getLineNumber());
              }
              break;
            case DATETIME:
              try {
                value = folder.getMappingDescriptor().getDatetimeFormat().parse(singleValue);
              }
              catch (ParseException e) {
                throw new CsvImportException("wrong date format", BAD_DATE_FORMAT_ERROR_KEY, singleValue, field.getName(), dataRow.getLineNumber());
              }
              break;
            case TIME:
              try {
                value = folder.getMappingDescriptor().getTimeFormat().parse(singleValue);
              }
              catch (ParseException e) {
                throw new CsvImportException("wrong date format", BAD_DATE_FORMAT_ERROR_KEY, singleValue, field.getName(), dataRow.getLineNumber());
              }
              break;
            case DIRECTORY:
              value = importDirectory(folderPath, field, singleValue);
              break;
            case FILE:
              value = importFile(folderPath, field, singleValue);
              break;
            default:
              // get the value.
              value = beanFactory.getTypeConverter().convertIfNecessary(singleValue, field.getFieldClass());
          }
        }
      }
      fieldsValues.put(field.getName(), value);
    }

    return fieldsValues;
  }



  /**
   * save the configured supported languages in database.
   * 
   * @param mappingDescriptor
   *          the {@link MappingDescriptor} describing the mapping
   * @throws MappingException
   *           if a mapping error occur
   * @throws PersistenceException
   *           if a persistence error occur
   */
  @Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = BundledException.class)
  public void saveSupportedLanguages(MappingDescriptor mappingDescriptor)
      throws MappingException, PersistenceException
  {
    for (Locale locale : mappingDescriptor.getSupportedLocales()) {
      if (getLanguage(locale) == null) {
        LanguageImpl language = new LanguageImpl();
        language.setLocale(locale);
        save(language);
      }

    }

  }



  /**
   * get from the database the {@link language} that corresponds to the given
   * locale.
   * 
   * @param locale
   *          the {@link Locale} to consider
   * @return the corresponding {@link Language}
   * @throws PersistenceException
   *           if an error occur during the query
   */
  private Language getLanguage(Locale locale)
      throws PersistenceException
  {
    for (Language language : getAll(Language.class)) {
      if (language.getLocale().equals(locale)) {
        return language;
      }
    }
    return null;
  }



  /**
   * setter for the dataContainerManager property.
   * 
   * @param dataContainerManager
   *          the dataContainerManager to set
   */
  public void setDataContainerManager(DataContainerManager dataContainerManager)
  {
    this.dataContainerManager = dataContainerManager;
  }



  /**
   * setter for the exceptionManager property.
   * 
   * @param exceptionManager
   *          the exceptionManager to set
   */
  public void setExceptionManager(ExceptionManager exceptionManager)
  {
    this.exceptionManager = exceptionManager;
  }



  /**
   * setter for the messageSource property.
   * 
   * @param messageSource
   *          the messageSource to set
   */
  public void setMessageSource(CustomResourceBundleMessageSource messageSource)
  {
    this.messageSource = messageSource;
  }



  /**
   * getter for the fillCriteriaBinderVisitor property.
   * 
   * @return the fillCriteriaBinderVisitor
   */
  public FillCriteriaBinderVisitor getFillCriteriaBinderVisitor()
  {
    return fillCriteriaBinderVisitor;
  }



  /**
   * setter for the fillCriteriaBinderVisitor property.
   * 
   * @param fillCriteriaBinderVisitor
   *          the fillCriteriaBinderVisitor to set
   */
  public void setFillCriteriaBinderVisitor(FillCriteriaBinderVisitor fillCriteriaBinderVisitor)
  {
    this.fillCriteriaBinderVisitor = fillCriteriaBinderVisitor;
  }



  /**
   * getter for the fieldCommentsGenerator property.
   * 
   * @return the fieldCommentsGenerator
   */
  public FieldCommentsGenerator getFieldCommentsGenerator()
  {
    return fieldCommentsGenerator;
  }



  /**
   * setter for the fieldCommentsGenerator property.
   * 
   * @param fieldCommentsGenerator
   *          the fieldCommentsGenerator to set
   */
  public void setFieldCommentsGenerator(FieldCommentsGenerator fieldCommentsGenerator)
  {
    this.fieldCommentsGenerator = fieldCommentsGenerator;
  }



  /**
   * getter for the checkIfEntityIsNullBinderVisitor property.
   * 
   * @return the checkIfEntityIsNullBinderVisitor
   */
  public CheckIfEntityIsNullBinderVisitor getCheckIfEntityIsNullBinderVisitor()
  {
    return checkIfEntityIsNullBinderVisitor;
  }



  /**
   * setter for the checkIfEntityIsNullBinderVisitor property.
   * 
   * @param checkIfEntityIsNullBinderVisitor
   *          the checkIfEntityIsNullBinderVisitor to set
   */
  public void setCheckIfEntityIsNullBinderVisitor(CheckIfEntityIsNullBinderVisitor checkIfEntityIsNullBinderVisitor)
  {
    this.checkIfEntityIsNullBinderVisitor = checkIfEntityIsNullBinderVisitor;
  }



  /**
   * getter for the fillNewEntityBinderVisitor property.
   * 
   * @return the fillNewEntityBinderVisitor
   */
  public FillNewEntityBinderVisitor getFillNewEntityBinderVisitor()
  {
    return fillNewEntityBinderVisitor;
  }



  /**
   * setter for the fillNewEntityBinderVisitor property.
   * 
   * @param fillNewEntityBinderVisitor
   *          the fillNewEntityBinderVisitor to set
   */
  public void setFillNewEntityBinderVisitor(FillNewEntityBinderVisitor fillNewEntityBinderVisitor)
  {
    this.fillNewEntityBinderVisitor = fillNewEntityBinderVisitor;
  }



  /**
   * getter for the executionActionVisitor property.
   * 
   * @return the executionActionVisitor
   */
  public ExecutionActionVisitor getExecutionActionVisitor()
  {
    return executionActionVisitor;
  }



  /**
   * setter for the executionActionVisitor property.
   * 
   * @param executionActionVisitor
   *          the executionActionVisitor to set
   */
  public void setExecutionActionVisitor(ExecutionActionVisitor executionActionVisitor)
  {
    this.executionActionVisitor = executionActionVisitor;
  }



  /**
   * getter for the computeColumnNameVisitor property.
   * 
   * @return the computeColumnNameVisitor
   */
  public ComputeColumnNameVisitor getComputeColumnNameVisitor()
  {
    return computeColumnNameVisitor;
  }



  /**
   * setter for the computeColumnNameVisitor property.
   * 
   * @param computeColumnNameVisitor
   *          the computeColumnNameVisitor to set
   */
  public void setComputeColumnNameVisitor(ComputeColumnNameVisitor computeColumnNameVisitor)
  {
    this.computeColumnNameVisitor = computeColumnNameVisitor;
  }



  /**
   * getter for the enumerationUtils property.
   * 
   * @return the enumerationUtils
   */
  public EnumerationUtils getEnumerationUtils()
  {
    return enumerationUtils;
  }



  /**
   * setter for the enumerationUtils property.
   * 
   * @param enumerationUtils
   *          the enumerationUtils to set
   */
  public void setEnumerationUtils(EnumerationUtils enumerationUtils)
  {
    this.enumerationUtils = enumerationUtils;
  }

}
